using GLib;
using Gee;

public class DirectoryMonitor : Object {

	public signal void change(File file, File? otherfile, FileMonitorEvent event);


	public DirectoryMonitor(File directory){
		mainMonitor = new monitoredDirectory (this, directory);
	}
	public void destroy(){
		mainMonitor.destroy();
	}

	monitoredDirectory mainMonitor;

	class monitoredDirectory{
		public monitoredDirectory(DirectoryMonitor boss, File file){
			monitor = file.monitor_directory (FileMonitorFlags.SEND_MOVED);
			path = file.get_path();
			this.boss = boss;

			handler_id = monitor.changed.connect(monitor_event);


			
			/*FileType type = file.query_file_type (0);
			if (type != FileType.DIRECTORY)//Create file monitors for directories recursively
				return;*/
			
			//monitoredDirectory monDir = new monitoredDirectory(boss, directory);

			FileEnumerator enumer;
			try{//Abort if children cannot be enumerated (possibly due to insufficient permissions)
				enumer = file.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME, 0);
			}catch{
				return;
			}

			FileInfo file_info;
			while(true) {
				file_info = enumer.next_file ();
				if (file_info == null)
					break;

				File iter = File.new_for_path (file.get_parse_name () + "/" + file_info.get_name ());
				if (!_fileIsVisible(iter))
					continue;
				FileType tpe = iter.query_file_type (0);
				if (tpe != FileType.DIRECTORY)
					continue;

				addChildMonitor(iter);
				//monitoredDirectory newMonDir = new monitoredDirectory(boss, iter);
				//if (newMonDir != null)
					//childDirs.add(newMonDir);
			}
		}

		void monitor_event(File file, File? otherfile, FileMonitorEvent event){

			bool dirInvolved = false;//Check if file or otherfile is directory (necessary for move directory checks)
			if (file != null)
				if (file.query_file_type (0) == FileType.DIRECTORY)
					dirInvolved = true;
			if (otherfile != null)
				if (otherfile.query_file_type (0) == FileType.DIRECTORY)
					dirInvolved = true;

			if (dirInvolved){//Add / remove monitors if directory is affected

				if (event == FileMonitorEvent.CREATED){//Remove or add watchers as necessary
					if (_fileIsVisible (file))
						addChildMonitor (file);
				}else if (event == FileMonitorEvent.MOVED){
					if (_fileIsVisible (file)){//Remove old monitor
						monitoredDirectory dir = getChildByPath (file.get_path());
						dir.destroy();
						childDirs.remove (dir);
					}
					if (_fileIsVisible (otherfile))//Add new monitor
						addChildMonitor (otherfile);

				}
			}

			if (event == FileMonitorEvent.MOVED){//If a file is moved from invisible to visible, send created signal
				if (!_fileIsVisible(file) && _fileIsVisible (otherfile)){
					boss.change(otherfile, null, FileMonitorEvent.CREATED);
					return;
				}
				if (_fileIsVisible(file) && !_fileIsVisible (otherfile)){
					boss.change(otherfile, null, FileMonitorEvent.DELETED);
					return;
				}
			}
			if (!_fileIsVisible (file))//Abort if file is invisible
				return;
			if (event == FileMonitorEvent.DELETED){
				monitoredDirectory dir = getChildByPath (file.get_path());
				if (dir != null){
					dir.destroy();
					childDirs.remove (dir);
					return;
				}
			}

			boss.change(file, otherfile, event);
		}

		DirectoryMonitor boss;
		string path;
		ulong handler_id;
		FileMonitor monitor;

		monitoredDirectory? getChildByPath(string path) {
			foreach (monitoredDirectory dir in childDirs)
				if (dir.path == path)
				return dir;
			return null;
		}
		void addChildMonitor(File file){
			if (getChildByPath (file.get_path()) == null)//Only add if not already in list
				childDirs.add(new monitoredDirectory(boss, file));
		}
		ArrayList<monitoredDirectory> childDirs = new ArrayList<monitoredDirectory>();

		internal void destroy(){
			monitor.disconnect(handler_id);
			foreach (monitoredDirectory child in childDirs)
				child.destroy();
		}


	}

}

public static bool _fileIsVisible(File file){
	if (file.get_basename().has_prefix(".") || file.get_basename().has_suffix("~"))//Ignore invisible files
		return false;
	return true;
}